// This is the main DLL file.

#include "ROOTDotNet.h"
#include "ROOTDOTNETVoidObject.hpp"

///
/// ROOT includes -- we have to dynamically walk the class list to figure out
/// what to do!
///
#include "TObject.h"
#include "TClass.h"
#include "TBranch.h"

#include "root_type_holder.hpp"

#include <string>
#include <map>

using std::map;
using std::string;
using namespace System;
using namespace System::Reflection;
using namespace System::Collections::Generic;

#ifdef nullptr
#undef nullptr
#endif

namespace ROOTNET {
	namespace Utility {

		///
		/// GetBestObject
		///
		///  Given a TObject pointer attempt to determine what type of object we are dealing with, and create it.
		///  Return the interface we are templated with.
		///
		///  NOTE: this only works if T is a pointer (i.e. TObject^).
		///
		///  This will always generate a new wrapper object.
		///
		generic<class T>
		where T: ref class
			T ROOTObjectServices::GetBestObject
			(const ::TObject *obj)
		{
			///
			/// Simple cases...
			///

			if (obj == 0) {
				return T();
			}

			///
			/// Determine the class type of the object that we are looking at.
			///

			TClass *cls = obj->IsA();
			if (cls == 0) {
				return T();
			}

			///
			/// Get the type that we can use to wrap this guy
			///

			Type ^class_type = root_type_holder::GetBestMatchType (cls);
			if (class_type == nullptr)
				throw gcnew System::InvalidOperationException("ROOT.NET can't find a good class to match this object - which should be impossible!");

			///
			/// Next find a ctor that takes only the C++ pointer as an argument. This is the ctor
			/// that is generated by the translation code. We will use this ctor to create the object.
			///

			array<ConstructorInfo^>^ ctors = class_type->GetConstructors();
			String ^class_name_ptr = class_type->Name->Substring(1) + "*";
			ConstructorInfo ^the_ctor;
			bool found_ctor = false;
			for (int i_ctor = 0; i_ctor < ctors->Length; i_ctor++) {
				array<ParameterInfo^>^ params = ctors[i_ctor]->GetParameters();
				if (params->Length != 1) {
					continue;
				}
				if (params[0]->ParameterType->FullName == class_name_ptr) {
					the_ctor = ctors[i_ctor];
					found_ctor = true;
					break;
				}
			}

			if (!found_ctor) {
				return T();
			}

			///
			/// Finally, we can box up the pointer and call the ctor.
			///

			array<Object^> ^arguments = gcnew array<Object^>(1);
			arguments[0] = Pointer::Box(const_cast<::TObject*>(obj), the_ctor->GetParameters()[0]->ParameterType);
			Object ^the_obj = the_ctor->Invoke (arguments);
			return (T) the_obj;
		}

		///
		/// Do wrapper looking and registration for a non-TObject object type.
		///
		///  This will always generate a new wrapper object.
		///
		ROOTDOTNETBaseTObject ^ ROOTObjectServices::GetBestNonTObjectObject (const void *obj, ::TClass *cls)
		{
			///
			/// Simple cases...
			///

			if (obj == 0) {
				return nullptr;
			}

			///
			/// Get the type that we can use to wrap this guy
			///

			Type ^class_type = root_type_holder::GetBestMatchType (cls);

			///
			/// Next find a ctor that takes only the C++ pointer as an argument. This is the ctor
			/// that is generated by the translation code. We will use this ctor to create the object.
			///

			bool found_ctor (false);
			ConstructorInfo ^the_ctor (nullptr);
			if (class_type != nullptr) {
				array<ConstructorInfo^>^ ctors = class_type->GetConstructors();
				String ^class_name_ptr = class_type->Name->Substring(1) + "*";
				for (int i_ctor = 0; i_ctor < ctors->Length; i_ctor++) {
					array<ParameterInfo^>^ params = ctors[i_ctor]->GetParameters();
					if (params->Length != 1) {
						continue;
					}
					if (params[0]->ParameterType->FullName == class_name_ptr) {
						the_ctor = ctors[i_ctor];
						found_ctor = true;
						break;
					}
				}
			}

			///
			/// See if we know about the ctor. If so, then we have a wrapper object and we should
			/// use that. Otherwise, we will need to use a basic dummy that gives access to everything
			/// via the dynamic facility.
			///

			if (found_ctor) {
				array<Object^> ^arguments = gcnew array<Object^>(1);
				arguments[0] = Pointer::Box(const_cast<void*>(obj), the_ctor->GetParameters()[0]->ParameterType);
				ROOTDOTNETBaseTObject ^the_obj = (ROOTDOTNETBaseTObject ^) the_ctor->Invoke (arguments);
				return the_obj;
			} else {
				class_type = ROOTDOTNETVoidObject::typeid;
				auto ctors = class_type->GetConstructors();
				for (int i_ctor = 0; i_ctor < ctors->Length; i_ctor++) {
					auto params = ctors[i_ctor]->GetParameters();
					if (params->Length != 2)
						continue;
					if (params[0]->ParameterType->FullName != "System.Void*")
						continue;
					if (params[1]->ParameterType->FullName != "TClass*")
						continue;
					the_ctor = ctors[i_ctor];
					break;
				}

				if (the_ctor == nullptr)
					throw gcnew System::InvalidOperationException ("Unable to find proper ctor on the ROOTDOTNETVoidObject object!!");

				auto arguments = gcnew array<Object^>(2);
				arguments[0] = Pointer::Box(const_cast<void*>(obj), the_ctor->GetParameters()[0]->ParameterType);
				arguments[1] = Pointer::Box(cls, the_ctor->GetParameters()[1]->ParameterType);
				ROOTDOTNETVoidObject ^the_obj = (ROOTDOTNETVoidObject ^) the_ctor->Invoke(arguments);
				return the_obj;
			}

		}

	}
}